﻿using PetaPoco;
using PetaPoco.Core;
using System.Data;
using System.Data.SqlClient;

namespace BlazorAdmin.Extensions
{
    /// <summary>
    /// SQLServer 数据库实现类
    /// </summary>
    internal class SqlServerBatchProvider : BatchProvider
    {
        private static readonly Func<IDatabase, SqlConnection> SqlConnectionResolver = (IDatabase db) => (SqlConnection)db.Connection;
        private static readonly Func<IDatabase, SqlTransaction> SqlTransactionResolver = (IDatabase db) => (SqlTransaction)db.Transaction;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="db">IDatabase 实例</param>
        public SqlServerBatchProvider(IDatabase db) : base(db) => SupportBulk = true;

        /// <summary>
        /// 获得/设置 条目阈值 默认 512
        /// </summary>
        public int Limit { get; set; } = 512;

        /// <summary>
        /// 批次插入业务实体类，根据Limit自动判断是Bulk插入还是Batch插入
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="pocos"></param>
        protected override void InsertBulk<T>(IEnumerable<T> pocos)
        {
            PocoData pd = PocoData.ForType(typeof(T), Database.DefaultMapper);
            if (pocos.Count() >= Limit)
            {
                InsertBulkImpl(pd, pocos);
            }
            else
            {
                InsertBatch(pd, pocos);
            }
        }

        /// <summary>
        /// 批次插入业务实体类，根据Limit自动判断是Bulk插入还是Batch插入
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="primaryKeyName"></param>
        /// <param name="autoIncrement"></param>
        /// <param name="pocos"></param>
        protected override void InsertBulk(string tableName, string primaryKeyName, bool autoIncrement, IEnumerable<object> pocos)
        {
            PocoData pocoData = PocoData.ForType(pocos.First().GetType(), Database.DefaultMapper);
            pocoData.TableInfo.TableName = tableName;
            if (!string.IsNullOrEmpty(primaryKeyName))
            {
                pocoData.TableInfo.PrimaryKey = primaryKeyName;
            }

            pocoData.TableInfo.AutoIncrement = autoIncrement;
            if (pocos.Count() >= Limit)
            {
                InsertBulkImpl(pocoData, pocos);
            }
            else
            {
                InsertBatch(pocoData, pocos);
            }
        }

        private void InsertBulkImpl<T>(PocoData pd, IEnumerable<T> pocos)
        {
            PocoData pd2 = pd;
            try
            {
                Database.OpenSharedConnection();
                using SqlBulkCopy sqlBulkCopy = new(SqlConnectionResolver(Database), SqlBulkCopyOptions.Default, SqlTransactionResolver(Database));
                DataTable dataTable = new();
                List<KeyValuePair<string, PocoColumn>> list = pd2.Columns.Where((KeyValuePair<string, PocoColumn> x) => !x.Value.ResultColumn && (!pd2.TableInfo.AutoIncrement || !x.Value.ColumnName.Equals(pd2.TableInfo.PrimaryKey, StringComparison.OrdinalIgnoreCase))).ToList();
                foreach (KeyValuePair<string, PocoColumn> item in list)
                {
                    sqlBulkCopy.ColumnMappings.Add(item.Value.PropertyInfo.Name, item.Value.ColumnName);
                    dataTable.Columns.Add(item.Value.PropertyInfo.Name, Nullable.GetUnderlyingType(item.Value.PropertyInfo.PropertyType) ?? item.Value.PropertyInfo.PropertyType);
                }

                foreach (T poco in pocos)
                {
                    object[] array = new object[list.Count];
                    for (int i = 0; i < array.Length; i++)
                    {
                        object obj = list[i].Value.GetValue(poco);
                        if (Database.DefaultMapper != null)
                        {
                            Func<object, object> toDbConverter = Database.DefaultMapper.GetToDbConverter(list[i].Value.PropertyInfo);
                            if (toDbConverter != null)
                            {
                                obj = toDbConverter(obj);
                            }
                        }

                        array[i] = obj;
                    }

                    dataTable.Rows.Add(array);
                }

                sqlBulkCopy.DestinationTableName = pd2.TableInfo.TableName;
                sqlBulkCopy.WriteToServer(dataTable);
            }
            finally
            {
                Database.CloseSharedConnection();
            }
        }
    }
}
